package com.stars.easyms.rest.handler;

import com.alibaba.fastjson.JSON;
import com.stars.easyms.base.annotation.CustomToString4Log;
import com.stars.easyms.base.encrypt.EasyMsEncrypt;
import com.stars.easyms.base.trace.EasyMsTraceSynchronizationManager;
import com.stars.easyms.base.util.*;
import com.stars.easyms.rest.bean.RestInfo;
import com.stars.easyms.rest.bean.EasyMsRestContext;
import com.stars.easyms.rest.exception.RestRuntimeException;
import com.stars.easyms.rest.initializer.RequestMappingPathForRestInfo;
import com.stars.easyms.rest.properties.EasyMsRestProperties;
import com.stars.easyms.swagger.properties.EasyMsSwaggerProperties;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.StringUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;

/**
 * <p>className: EasyMsRestMessageHandler</p>
 * <p>description: EasyMs的消息转换器</p>
 *
 * @author guoguifang
 * @version 1.2.2
 * @date 2019-08-05 14:26
 */
@SuppressWarnings("unchecked")
final class EasyMsRestMessageHandler {

    private static final Logger logger = LoggerFactory.getLogger(EasyMsRestMessageHandler.class);

    private final EasyMsRestProperties easyMsRestProperties;

    private HttpMessageConverter fastJsonHttpMessageConverter;

    private HttpMessageConverter mappingJackson2HttpMessageConverter;

    private EasyMsSwaggerProperties easyMsSwaggerProperties;

    EasyMsRestMessageHandler(EasyMsRestProperties easyMsRestProperties) {
        this.easyMsRestProperties = easyMsRestProperties;
        this.fastJsonHttpMessageConverter = FastJsonUtil.getFastJsonHttpMessageConverter();
        this.mappingJackson2HttpMessageConverter = ApplicationContextHolder.getApplicationContext().getBean(MappingJackson2HttpMessageConverter.class);
        this.easyMsSwaggerProperties = ApplicationContextHolder.getBean(EasyMsSwaggerProperties.class);
    }

    /**
     * 解析request body体中的json格式数据
     */
    void parseRequestBody(EasyMsRestContext easyMsRestContext) throws Exception {

        // 解析请求体
        String bodyStr;
        try {
            HttpInputMessage inputMessage = new ServletServerHttpRequest(
                    new BodyReaderHttpServletRequestWrapper(easyMsRestContext.getHttpServletRequest()));

            // 如果默认使用fastJson解析，首先用fastJson解析，若报错则使用mappingJackson解析，如果默认不使用fastJson解析则互换，异常后inputMessage会关闭需要重新创建
            if (easyMsRestProperties.isFastJson()) {
                try {
                    bodyStr = (String) fastJsonHttpMessageConverter.read(String.class, inputMessage);
                } catch (HttpMessageNotReadableException e) {
                    bodyStr = (String) mappingJackson2HttpMessageConverter.read(String.class, inputMessage);
                }
            } else {
                try {
                    bodyStr = (String) mappingJackson2HttpMessageConverter.read(String.class, inputMessage);
                } catch (HttpMessageNotReadableException e) {
                    bodyStr = (String) fastJsonHttpMessageConverter.read(String.class, inputMessage);
                }
            }
        } catch (Exception ex) {

            // 如果解析json失败则抛出Rest运行时异常
            throw new RestRuntimeException("I/O error while reading input message", ex);
        }

        // 如果bodyStr为空则给一个默认的json值
        if (!StringUtils.hasText(bodyStr)) {
            bodyStr = "{}";
        }

        // 判断是否需要解密，若需要解密则执行解密操作
        RequestMappingPathForRestInfo requestMappingPathForRestInfo = easyMsRestContext.getRequestMappingPathForRestInfo();
        if (requestMappingPathForRestInfo.isEncrypt()) {

            // 为了防止解密异常，此处需要记录解密前的请求信息
            if (easyMsRestProperties.isLogRequest()) {
                logger.info("Received encrypted request data: {}", bodyStr);
            }

            // 使用aes算法解密
            bodyStr = EasyMsEncrypt.decode(bodyStr, requestMappingPathForRestInfo.getSecret(),
                    requestMappingPathForRestInfo.getIv(), requestMappingPathForRestInfo.getEncryptRequestKey());
        }
        easyMsRestContext.setRequestBodyStr(bodyStr);

        // 根据传入参数确定restInfo
        RestInfo restInfo = requestMappingPathForRestInfo.getRestInfo(bodyStr);
        if (restInfo == null) {
            throw new RestRuntimeException("The rest service for '{}' can't be found! Received request data: {}",
                    easyMsRestContext.getEasyMsRequestEntity().getRequestPath(), bodyStr);
        }
        easyMsRestContext.setRestInfo(restInfo);

        // 将请求参数转换成对应input对象
        Object input = null;
        Class<?> parameterType = restInfo.getParameterType();
        if (parameterType != Void.class) {
            try {

                input = JSON.parseObject(bodyStr, parameterType);
                easyMsRestContext.setRequestBody(input);
            } catch (Exception e) {

                // 记录接收服务错误的请求参数并抛出异常
                throw new RestRuntimeException("The request data '{}' could not be converted to the type '{}'!",
                        bodyStr, parameterType.getName(), e);
            }
        }

        // 记录接收服务日志信息，若有解密此处记录的是解密后的请求信息
        if (easyMsRestProperties.isLogRequest() && !JsonUtil.checkAndReturnIsBlankJson(bodyStr)) {
            logger.info("Received request data: {}",
                    parameterType.isAnnotationPresent(CustomToString4Log.class) && input != null ? input.toString() : bodyStr);
        }

        // 如果header头中找不到用户信息，则判断是否开启了在线debug模式(主要用于开发测试环境debug方便)，如果开启了则从参数中获取对一个用户信息
        if (EasyMsTraceSynchronizationManager.getCurrentUserInfo() == null &&
                easyMsSwaggerProperties != null && easyMsSwaggerProperties.isOnlineDebug()) {
            Object body = JSON.parseObject(bodyStr);
            if (body != null) {
                Object userInfo = BeanUtil.copyOf(body, restInfo.getUserInfoClass());
                if (userInfo != null) {
                    String userInfoStr = JsonUtil.toJSONString(userInfo);
                    EasyMsTraceSynchronizationManager.setUserInfo(
                            Base64.encodeBase64String(userInfoStr.getBytes(StandardCharsets.UTF_8)), userInfoStr);
                    logger.info("Current requested user information: {}", userInfoStr);
                }
            }
        }
    }

    private static class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {

        private final byte[] body;

        private BodyReaderHttpServletRequestWrapper(HttpServletRequest request) {
            super(request);
            body = getBodyString(request).getBytes(StandardCharsets.UTF_8);
        }

        @Override
        public BufferedReader getReader() {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }

        @Override
        public ServletInputStream getInputStream() {

            final ByteArrayInputStream bais = new ByteArrayInputStream(body);

            return new ServletInputStream() {

                @Override
                public int read() {
                    return bais.read();
                }

                @Override
                public boolean isFinished() {
                    return false;
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setReadListener(ReadListener readListener) {

                }
            };
        }

        private String getBodyString(HttpServletRequest request) {
            StringBuilder sb = new StringBuilder();
            try (InputStream inputStream = request.getInputStream();
                 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
            } catch (IOException e) {
                logger.warn("Get Request Body fail！", e);
            }
            return sb.toString();
        }
    }

}